<?php
/**
* @package mosRest
* @author Chad Auld and Ozgur Cem Sen (code@brilaps.com)
* @copyright Brilaps, LLC (http://brilaps.com)
* @link http://brilaps.com || http://wiki.brilaps.com
* @license http://www.opensource.org/licenses/gpl-license.php GNU/GPL v.2.
*/

class mosRest extends mosDBTable {
	/** @public int Primary key */
	public $id=null;
	/** @public string */
	public $developer_name=null;
	/** @public string */
	public $product_name=null;
	/** @public string */
	public $web_app_url=null;
	/** @public string */
	public $contact_email=null;
	/** @public string */
	public $phone_number=null;
	/** @public string */
	public $description=null;
	/** @public string */
	public $activation=null;
	/** @public string */
	public $block=null;
	/** @public string */
	public $registration_date=null;
	/** @public string */
	public $approval_date=null;

	/**
	* @param database A database connector object
	*/
	function mosRest() {
	    global $database;
		$this->mosDBTable( '#__rest', 'id', $database );
	}
}

class mosRestLog extends mosDBTable {
	/** @public int Primary key */
	public $id=null;
	/** @public string */
	public $request_key=null;
	/** @public string */
	public $request_type=null;
	/** @public string */
	public $request_uri=null;
	/** @public string */
	public $request_timestamp=null;

	/**
	* @param database A database connector object
	*/
	function mosRestLog() {
	    global $database;
		$this->mosDBTable( '#__rest_log', 'id', $database );
	}
}

/**
* This class brings RESTful functionality to the MiaCMS.  This is just a base class that handles many of the 
* key items needed to implement REST.  It should be used as a building block to RESTify MiaCMS components.  We
* have also released a RESTified com_content (com_rest_content).  You can use that as an example to extend additional 
* components.  Each component will need to implement its own data retrival routines.
*/
class mRest {
	
	static $apiVersion = '1.0';
	
	/** 
	* Handles the actual REST response 
	* @param htmlReturnStatusCode - html request status code (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
	* @param returnFormat - response format requested be user (ie) JSON, XML, PHP
	* @param data - data to be sent back
	*/
	function restResponse($htmlReturnStatusCode, $returnFormat, $data) {	    
	    //Produce the final output
    	header( $this->setHtmlStatusCode($htmlReturnStatusCode) );
    	header( 'Content-Type: '.$this->setReturnContentType($returnFormat).'; charset=utf-8' );
    	//Force the browser to use fresh data
    	header( 'Expires: Mon, 26 Jul 1997 05:00:00 GMT' );
    	header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
    	header( 'Cache-Control: no-store, no-cache, must-revalidate' );
    	header( 'Cache-Control: post-check=0, pre-check=0', false );
    	header( 'Pragma: no-cache' );
    	echo $data;
    	exit;
	}
	
	/**
	* Handles encoding of the final REST response
	* @param data - data to be encoded
	* @param returnFormat - return format requested by the client (i.e.) json, xml, or php
	* @param callback - optional JavaScript callback function to wrap request with
	*/
	function encodeRestResponse($data, $returnFormat, $callback='') {
		global $mosConfig_absolute_path;
	    if ($this->validateReturnFormat($returnFormat)===false) {
    	    $returnFormat='json'; //force json
    	}
    	
	    if ($returnFormat=='json') {
	        /* Handles JSON encoding using the standard PEAR class (native in later version of PHP5, but we 
	            want to support all v5 installs) */
            if (file_exists($mosConfig_absolute_path.'/includes/PEAR/JSON/json.php')) {
                require($mosConfig_absolute_path.'/includes/PEAR/JSON/json.php');
            } else {
                require('json.php');
            }
    		$json = new Services_JSON();
	        $returnData = $json->encode($data);
	        //Wrap in JS callback function if requested
    		if (!empty($callback)) {
    			$returnData = $callback.'( '.$returnData.' )';
    		}
	    } else if ($returnFormat=='php') {
	        $returnData = serialize($data);
	    } else if ($returnFormat=='xml') {
	        //We expect a fully formed XML doc so nothing else is needed in theis case
	        $returnData = $data;
	    }
	    
		return $returnData;
	}
	
	/**
	* Verifies that the appid passed with the request is valid for tracking purposes
	* @param appId - application id assigned to developer during registration
	* @param requestType - type of request being made (ex) content, search, rank, popular, etc
	* @param uri - the full url with query params to log with request (best obtained by using the full_url() function)
	*/
	function verifyRestAPIKey($appId, $requestType, $uri) {
		global $database, $mosConfig_absolute_path;
		
		//Include REST settings
    	$log_api_requests = '';
    	require($mosConfig_absolute_path.'/administrator/components/com_rest/config.rest.php');
    	//Log request for tracking purposes if enabled
    	if ($log_api_requests=='1') {
    		$this->logAPIRequest($appId, $requestType, $uri);
    	}
		
		$appId = $database->getEscaped($appId);
		$database->setQuery("SELECT id FROM #__rest WHERE rest_key='{$appId}'"
						  . "\n AND block=0");
		if (!$database->loadResult()) {
			return false;
		}
	}
	
	/**
	* Used to verify the requested JSON callback function name is valid
	*
	* The following characters are allowed: A-Z, a-z, 0-9, [], and _
	* Start with a simple ctype check since it's a lighter process and more common.
	* Fall back on character checking if the whole this isn't alphanumeric
	*
	* @param callback - the requested JSON callback function name
	*/
	function verifyJSONCallbackName($callbackName) {
		if (ctype_alpha($callbackName)) {
			return true;
		} else {
			$chrCount=strlen($callbackName);
			$altChars=array('[',']','_');
			for ($i=0;$i<$chrCount;$i++) {
				if ((!ctype_alnum($callbackName[$i])) && (!in_array($callbackName[$i], $altChars))) {
					return false;
				}
			}
		}
	}
	
	/**
	* Log API request for REST usage tracking purposes (normally called on by verifyRestAPIKey)
	* @param appId - application id assigned to developer during registration
	* @param requestType - type of request being made (ex) content, search, rank, popular, etc
	* @param uri - the full url with query params to log with request (best obtained by using the full_url() function)
	*/
	function logAPIRequest($appId, $requestType, $uri) {
		global $database;
		
		$row = new mosRestLog( $database );
		$row->id = 0;
		$row->rest_key = $database->getEscaped($appId);
		$row->request_type = $database->getEscaped($requestType);
		$row->request_uri = $database->getEscaped($uri);
		$row->request_timestamp = date("Y-m-d H:i:s");

		if (!$row->store()) {
			die('Unable to log request: '.$row->getError());
		}
	}
	
	/**
	* Builds the error document used in negative responses.
	* @param errorMessage - custom message to be wrapped in the error doc
	* @param returnFormat - error doc sent back in the user defined return format
	*/
	function restError($errorMessage, $returnFormat='json') {
	    if ($this->validateReturnFormat($returnFormat)===false) {
    	    $returnFormat='json'; //force json
    	}
	    
	    if ($returnFormat=='json' || $returnFormat=='php') {
	        $returnMessage = array('Error'=>array('Message'=>$errorMessage));
	    } else if ($returnFormat=='xml') {
			//Send back XML
			$dom = new DOMDocument("1.0");
			//Create the root element
			$root = $dom->createElement("Error");
			$dom->appendChild($root);
			//Create child elements
			$message = $dom->createElement("Message");
			$root->appendChild($message);
			$message->appendChild($dom->createTextNode("$errorMessage"));
			//Save xml tree
			$returnMessage = $dom->saveXML();
		}
		
		$data = $this->encodeRestResponse($returnMessage, $returnFormat);
		return $data;
	}
	
	/**
	* Validate requested return format is one of the supported types
	* @param returnFormat - client requested output format
	*/
	function validateReturnFormat($returnFormat) {
	    $validReturnFormat = array('json', 'xml', 'php');
	    if (!in_array($returnFormat, $validReturnFormat)) {
    		return false;
    	}
	}
	
	/**
	* Returns version number of the REST API.  Needed so that developers can code to different versions of the API.
	* @param returnFormat - error doc sent back in the user defined return format
	*/
	function getAPIVersion($returnFormat) {
		$version = self::$apiVersion;
		
		if ($this->validateReturnFormat($returnFormat)===false) {
    	    $returnFormat='json'; //force json
    	}
    	
    	if ($returnFormat=='json' || $returnFormat=='php') {
	        $returnMessage = array('API'=>array('Version'=>$version));
	    } else if ($returnFormat=='xml') {
			//Send back XML
			$dom = new DOMDocument("1.0");
			//Create the root element
			$root = $dom->createElement("API");
			$dom->appendChild($root);
			//Create child elements
			$message = $dom->createElement("Version");
			$root->appendChild($message);
			$message->appendChild($dom->createTextNode("$version"));
			//Save xml tree
			$returnMessage = $dom->saveXML();
		}
		
		$data = $this->encodeRestResponse($returnMessage, $returnFormat);
		return $data;
	}
	
	/**
	* Used to aid in the building of the HTTP header to send back with the request response
	* @param htmlStatusCode - the code to sent back
	*/
	function setHtmlStatusCode($htmlStatusCode) {
		$statusMessages = array(200=>'OK', 400=>'Bad Request', 401=>'Unauthorized');	
		if (array_key_exists($htmlStatusCode, $statusMessages)) {
		    $statusText = 'HTTP/1.1 '.$htmlStatusCode.' '.$statusMessages[$htmlStatusCode];
			return $statusText;
		} else {
		    //Fix to allow compatibility with J!
            if (!function_exists( 'T_' )) { function T_($string) {return $string;} }
			die(T_('Invalid Html Response Code!'));
		}
	}
	
	/**
	* Set HTTP header content type to send back with the request response
	* @param returnFormat - user requested return format
	*/
    function setReturnContentType($returnFormat) {
		//Determine return header content type
		switch ($returnFormat) {
			case 'json':
			case 'php':
				$ctype = 'text/html';
			break;

			case 'xml':
				$ctype = 'text/xml';
			break;
			
			default:
				$ctype = 'text/html';
			break;
		}
		return $ctype;
	}
	
	/**
	* Converts and object to an array
	* Code comes from PHP.net comments - http://us2.php.net/manual/en/function.get-object-vars.php#62470 
	*/
	function object_to_array($obj) {
		$_arr = is_object($obj) ? get_object_vars($obj) : $obj;
		foreach ($_arr as $key => $val) {
	    	$val = (is_array($val) || is_object($val)) ? $this->object_to_array($val) : $val;
	        $arr[$key] = $val;
	    }
		return $arr;
	}
	
	/**
	* Returns the full URL of the current page - including any query parameters.
	* Code comes from snipplr.com - http://snipplr.com/view/2734/get-full-url/
	*/
	function full_url() {
    	$s = empty($_SERVER["HTTPS"]) ? '' : ($_SERVER["HTTPS"] == "on") ? "s" : "";
    	$protocol = substr(strtolower($_SERVER["SERVER_PROTOCOL"]), 0, strpos(strtolower($_SERVER["SERVER_PROTOCOL"]), "/")) . $s;
    	$port = ($_SERVER["SERVER_PORT"] == "80") ? "" : (":".$_SERVER["SERVER_PORT"]);
    	return $protocol . "://" . $_SERVER['SERVER_NAME'] . $port . $_SERVER['REQUEST_URI'];
    }
	
}

?>